Closure
Closures can capture and store references to any constants and variables from the context in which they are defined. This is known as closing over those constants and variables.
Closure Expressions
.Sort
- For characters in strings, “greater than” means “appears later in the alphabet than”. This means that the letter “B” is “greater than” the letter “A”, and the string “Tom” is greater than the string “Tim”.
- The
sorted(by:)
method accepts a closure that takes two arguments of the same type as the array’s contents, and returns a Bool value to say whether the first value should appear before or after the second value once the values are sorted. - The sorting closure needs to return true if the first value should appear before the second value, and false otherwise.
1 | let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] |
Closure Expression Syntax
- The parameters in closure expression syntax can be in-out parameters, but they can’t have a default value.
- Variadic parameters can be used if you name the variadic parameter.
- Tuples can also be used as parameter types and return types.
1 | { (parameters) -> return type in |
Operator Methods
Swift’s String type defines its string-specific implementation of the greater-than operator (>) as a method that has two parameters of type String, and returns a value of type Bool.
1 | reversedNames = names.sorted(by: >) |
Trailing Closures
- function’s final argument
- don’t write the argument label for the closure
1 | func someFunctionThatTakesAClosure(closure: () -> Void) { |
If a closure expression is provided as the function or method’s only argument and you provide that expression as a trailing closure, you do not need to write a pair of parentheses ()
1 | reversedNames = names.sorted { $0 > $1 } |
.map
1 | let digitNames = [ |
Capturing Values
A closure can capture constants and variables from the surrounding context in which it is defined
1 | func makeIncrementer(forIncrement amount: Int) -> () -> Int { |
Closures Are Reference Types
- functions and closures are reference types.
- Whenever you assign a function or a closure to a constant or a variable, you are actually setting that constant or variable to be a reference to the function or closure
- This also means that if you assign a closure to two different constants or variables, both of those constants or variables will refer to the same closure:
1 | let alsoIncrementByTen = incrementByTen |
Escaping Closures
- A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns.
- One way that a closure can escape is by being stored in a variable that is defined outside the function
- many functions that start an asynchronous operation take a closure argument as a completion handler. The function returns after it starts the operation, but the closure isn’t called until the operation is completed—the closure needs to escape, to be called later
1 | var completionHandlers: [() -> Void] = [] |
Autoclosures
- An autoclosure is a closure that is automatically created to wrap an expression that’s being passed as an argument to a function. It doesn’t take any arguments, and when it’s called, it returns the value of the expression that’s wrapped inside of it.
- It’s common to call functions that take autoclosures, but it’s not common to implement that kind of function.
- An autoclosure lets you delay evaluation, because the code inside isn’t run until you call the closure.
- Overusing autoclosures can make your code hard to understand. The context and function name should make it clear that evaluation is being deferred.
Example 1
1 | var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] |
Example 2
1 | // customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] |
@autoclosure
1 | // customersInLine is ["Ewa", "Barry", "Daniella"] |
@autoclosure + @escaping
1 | // customersInLine is ["Barry", "Daniella"] |